<?php
/**
 * This file is part of Hyperf Mall.
 *
 * @link     https://gitee.com/scwlkj/hyperf_mall
 * @contact  hyperf_mall@163.com
 */

declare(strict_types=1);

namespace App\Common;

use App\Exception\BaseException;
use Hyperf\Redis\Redis;
use Hyperf\Utils\ApplicationContext;
use Hyperf\Utils\Context;
use Psr\Container\ContainerExceptionInterface;
use Psr\Container\NotFoundExceptionInterface;

class OidBuild
{
    use Singleton;

    public static $cacheKeyPx = 'common:oid:px';

    /**
     * 缓存类型
     * @var string
     */
    public static $cacheType = 'default';

    const RETRY_TIMES = 3;    //重试次数

    /**获取订单号
     * Notes:
     * Author: Bruce.z
     * DateTime: 2022/7/12 10:33
     * @return string
     * @throws BaseException
     * @throws ContainerExceptionInterface
     * @throws NotFoundExceptionInterface
     */
    public function getOid(): string
    {
        for ($i = 0; $i < self::RETRY_TIMES; $i++) {
            $oid = $this->mkOid();
            if ($this->checkOid($oid)) {
                return $oid;
            }
        }
        throw new BaseException(BaseException::BASE_ERROR, '生成订单出错，请再试一次');
    }

    /** 生成商户批次号
     * Notes:
     * Author: Bruce.z
     * DateTime: 2022/7/12 10:33
     * @return string
     * @throws BaseException
     * @throws ContainerExceptionInterface
     * @throws NotFoundExceptionInterface
     */
    public function getMchBatchNo(): string
    {
        for ($i = 0; $i < self::RETRY_TIMES; $i++) {
            $payId = $this->mkMchBatchNo();
            if ($this->checkOid($payId)) {
                return $payId;
            }
        }
        throw new BaseException(BaseException::BASE_ERROR, '生成商户批次号出错，请再试一次');
    }

    /**
     * Notes: 生成支付单id
     * Author: Bruce.z
     * DateTime: 2022/7/12 10:33
     * @param $member_id
     * @return string
     * @throws BaseException
     * @throws ContainerExceptionInterface
     * @throws NotFoundExceptionInterface
     */
    public function getPayId($member_id): string
    {
        for ($i = 0; $i < self::RETRY_TIMES; $i++) {
            $payId = $this->mkPayId($member_id);
            if ($this->checkOid($payId)) {
                return $payId;
            }
        }
        throw new BaseException(BaseException::BASE_ERROR, '生成支付单出错，请再试一次');
    }

    /**
     * Notes: 生成退款单号
     * Author: Bruce.z
     * DateTime: 2022/7/12 10:33
     * @param $member_id
     * @return string
     * @throws BaseException
     * @throws ContainerExceptionInterface
     * @throws NotFoundExceptionInterface
     */
    public function getRefundNo($member_id): string
    {
        for ($i = 0; $i < self::RETRY_TIMES; $i++) {
            $payId = $this->mkPayId($member_id);
            if ($this->checkOid($payId)) {
                return $payId;
            }
        }
        throw new BaseException(BaseException::BASE_ERROR, '生成付款单号出错，请再试一次');
    }

    /**
     * Notes: 生成退款单号
     * Author: Bruce.z
     * DateTime: 2022/7/12 10:33
     * @return string
     * @throws BaseException
     * @throws ContainerExceptionInterface
     * @throws NotFoundExceptionInterface
     */
    public function getAfterNumber(): string
    {
        for ($i = 0; $i < self::RETRY_TIMES; $i++) {
            $payId = $this->mkAfterNumber();
            if ($this->checkOid($payId)) {
                return $payId;
            }
        }
        throw new BaseException(BaseException::BASE_ERROR, '生成售后单号出错，请再试一次');
    }

    /**
     * Notes:制作支付单号
     * @param $member_id
     * @param int $length
     * @return string
     */
    public function mkPayId($member_id, int $length = 19): string
    {
        $member_id = (string)$member_id;
        $time = time();
        $memberIdCount = strlen($member_id);
        if ($memberIdCount != 5) {     //保留5位
            $member_id = $memberIdCount > 5 ? substr($member_id, -5) : str_pad($member_id, 5, '0', STR_PAD_LEFT);
        }
        $m = $length - 15;    //去掉固定15位
        $rand_num_min = 0;
        $rand_num_max=str_repeat('9', $m);
        return $time . $member_id . sprintf("%0".$m."d", rand(intval($rand_num_min), intval($rand_num_max)));
    }

    /**
     * Notes:生成订单号
     * @param int $length 长度 固定19位，一般不变化
     * @return string
     */
    private function mkOid(int $length = 19): string
    {
        $time = time();
        $ymd = date('Ymd', $time);   //已年月日开始
        $seconds = $time - strtotime($ymd . '000000');    //计算当天秒数
        $seconds = str_pad(strval($seconds), 5, "0", STR_PAD_LEFT);   //秒数补零到固定5位
        $m = $length - 13;    //去掉固定13位
        $rand_num_min = 0;
        $rand_num_max=str_repeat('9', $m);
        return $ymd . $seconds . sprintf("%0".$m."d", rand(intval($rand_num_min), intval($rand_num_max)));
    }

    /**
     * Notes:生成商户批次号
     * @param int $length 长度 固定19位，一般不变化
     * @return string
     */
    private function mkMchBatchNo(int $length = 19): string
    {
        $time = time();
        $ymd = date('Ymd', $time);   //已年月日开始
        $seconds = $time - strtotime($ymd . '000000');    //计算当天秒数
        $seconds = str_pad(strval($seconds), 5, "0", STR_PAD_LEFT);   //秒数补零到固定5位
        $m = $length - 13;    //去掉固定13位
        $rand_num_min = 0;
        $rand_num_max=str_repeat('9', $m);
        return $ymd . $seconds . sprintf("%0".$m."d", rand(intval($rand_num_min), intval($rand_num_max)));
    }

    /**
     * Notes:生成售后编号
     * @param int $length 长度 固定19位，一般不变化
     * @return string
     */
    private function mkAfterNumber(int $length = 19): string
    {
        $time = time();
        $ymd = date('Ymd', $time);   //已年月日开始
        $seconds = $time - strtotime($ymd . '000000');    //计算当天秒数
        $seconds = str_pad(strval($seconds), 5, "0", STR_PAD_LEFT);   //秒数补零到固定5位
        $m = $length - 13;    //去掉固定13位
        $rand_num_min = 0;
        $rand_num_max=str_repeat('9', $m);
        return $ymd . $seconds . sprintf("%0".$m."d", rand(intval($rand_num_min), intval($rand_num_max)));
    }

    /**
     * Notes: 验证oid是否可用
     * Author: Bruce.z
     * DateTime: 2022/7/12 10:32
     * @param $oid
     * @return bool
     * @throws ContainerExceptionInterface
     * @throws NotFoundExceptionInterface
     */
    private function checkOid($oid): bool
    {
        $key = self::getCacheKey($oid);
        $redis = ApplicationContext::getContainer()->get(Redis::class);
        if (empty($redis->setnx($key, 1))) return false;
        $redis->expire($key, 5);
        return true;
    }

    /**
     * Notes:key处理
     * @param $oid string 订单号
     * @return string
     */
    private static function getCacheKey(string $oid): string
    {
        return static::$cacheKeyPx . ':' .$oid;
    }
}
